//
// Created by lxx on 23-1-11.
//
#define SOLAR_BUILD_CUDA_MULTI_PANEL_RECEIVER
#include "CudaMultiPanelReceiver.cuh"
#include <algorithm/rayTracing/qmcrt_util/global_constant.h>
#include <algorithm/rayTracing/qmcrt_util/global_function.cuh>
#include <algorithm/rayTracing/qmcrt_util/vector_arithmetic.cuh>

#include <cmath>

using namespace solar::cuda;

void CudaMultiPanelReceiver::CInit(float pixel_per_meter_for_receiver)
{
    pixel_length_ = 1.0f / float(pixel_per_meter_for_receiver);
    Cset_resolution(pixel_per_meter_for_receiver);
    Calloc_image();
    Cclean_image_content();
}
void CudaMultiPanelReceiver::Cset_resolution(float pixel_per_meter_for_receiver)
{

    resolution_.x = 2 * tube_ * panel_;
    resolution_.y = ceil(size_.y * float(pixel_per_meter_for_receiver));
    printf("x %d y %d pixel_per_meter_for_receiver %f  size_.y %f\n", resolution_.x, resolution_.y,
           pixel_per_meter_for_receiver, size_.y);
}
auto CudaMultiPanelReceiver::getFocusCenter(const float3& heliostat_position) -> float3
{
    if (isInsideCylinder(heliostat_position))
    {
        // If the heliostat position is inside cylinder, return the receiver position
        return pos_;
    }

    float3 dir = normalize(heliostat_position - pos_);
    float radius = size_.x / (length(make_float2(dir.x, dir.z)));

    float x = pos_.x + dir.x * radius;
    float z = pos_.z + dir.z * radius;
    //    printf("focus %f %f %f\n",x,pos_.y,z);
    return make_float3(x, pos_.y, z);
}

__device__ __host__ auto CudaMultiPanelReceiver::GIntersect(const float3& orig, const float3& dir,
                                                            float& t, float& u, int& panel_no,
                                                            int& tube_no) -> bool
{
    if (isInsideCylinder(orig))
    {
        // printf("The origin is inside the cylinder!!!\n");
        return false;
    }

    // 先和外圆柱求交，确定交在哪个面板上
    // 再和面板（矩形）求交，确定交在从左到右哪个像素中
    // True: return panel_no tube_no
    // 先和大圆柱求交
    float3 poss = pos_;
    float panel_angle = 2 * MATH_PI / float(panel_);
    float R = size_.x;
    float2 Ro = make_float2(poss.x - orig.x, poss.z - orig.z);
    float tp = dot(Ro, normalize(make_float2(dir.x, dir.z)));
    float delta = dot(Ro, Ro) - tp * tp;

    // Return false if:
    //  1) The direction is different
    //  2) Has no intersection
    float R2 = (R) * (R); // radius ^ 2
    if (tp < -Epsilon || delta > R2)
    {
        return false;
    }
    // remain and calculate the positive solution
    float t_plus = delta <= 0.0F ? (R) : sqrt(R2 - delta);
    t = (tp - t_plus) / length(make_float2(dir.x, dir.z));

    // calculate intersection point
    float3 intersect_pos_1 = t * dir + orig;
    float h_rece = size_.y;
    //        u = (intersect_pos_1.y - pos_.y) / h_rece + 0.5f;
    //        if(u < 0.0f || u > 1.0f){
    //            return false;
    //        }
    float y_abs = abs(intersect_pos_1.y - pos_.y);
    if (y_abs > h_rece / 2 + 2)
    {
        return false;
    }
    float3 io = intersect_pos_1 - make_float3(0, intersect_pos_1.y, 0);
    // IO is the vector from center of the cylinder to intersect pos 1
    // theta_IO is the angle from (1,0,0) East to IO
    //        float3 east= make_float3(1,0,0);
    float3 north = make_float3(0, 0, -1);
    float3 rho = cross(io, north);
    float theta_io = 0;

    if (io.x > 0)
    {
        theta_io = dot(io, north) / length(io);
        theta_io = acos(theta_io);
    }
    else
    {
        theta_io = dot(io, north) / length(io);
        theta_io = 2 * MATH_PI - acos(theta_io);
    }
    panel_no = int(floor(theta_io / panel_angle)) % panel_;
    float panel_up_theta = float(panel_no) * panel_angle;
    float panel_down_theta = float(panel_no + 1.0) * panel_angle;

    float up_x = sin(panel_up_theta) * R;
    float up_z = -cos(panel_up_theta) * R;
    float down_x = sin(panel_down_theta) * R;
    float down_z = -cos(panel_down_theta) * R;

    float Height = poss.y;
    float half_H = size_.y / 2;

    float3 panel_up = make_float3(up_x, Height + half_H, up_z);
    float3 panel_down = make_float3(down_x, Height + half_H, down_z);
    float3 mid = make_float3((up_x + down_x) / 2, panel_up.y, (up_z + down_z) / 2);
    float3 normal_to_cylinder = make_float3(mid.x, 0, mid.z);
    normal_to_cylinder = normalize(normal_to_cylinder);
    float3 panel_C = make_float3(panel_up.x, panel_up.y - size_.y, panel_up.z);
    // Down   UP
    //
    //        Panel_C(x,z same with up, but y lower than up)
    float t_ = NAN;
    float u_ = NAN;
    float v_ = NAN;
    bool is_intersect_with_margin =
        global_func::rayParallelogramIntersect(orig, dir, panel_up, panel_down, panel_C, t, u_, v_);
    if (!is_intersect_with_margin)
    {
        return false;
    }
    else
    {
        //                return false;
        // 只能和最左边或者最右边相交
        float3 intersect_pos_rect = orig + dir * t;
        // dir intersect with panel
        //  next to judge the no of little cylinder
        // litter cylinder raidus  and
        float uu_ = (intersect_pos_rect.y - poss.y) / (size_.y) + 0.5f;
        if (uu_ < 0.0f || uu_ >= 1.0f)
        {
            return false;
        }
        float abs_yy = abs(intersect_pos_rect.y - pos_.y);
        if (abs_yy > h_rece / 2)
        {
            return false;
        }
        tube_no = int(tube_ * 2 * u_);
        //            printf("panel=%d tube=%d\n",panel_no,tube_no);
        u = v_;
        t = t;

        return true;
    }
}

__device__ __host__ auto CudaMultiPanelReceiver::isInsideCylinder(const float3& orig) -> bool
{
    float2 l = make_float2(orig.x - pos_.x, orig.z - pos_.z);
    return dot(l, l) <= size_.x * size_.x;
}
